using System;
using System.Collections.Generic;
using System.Windows.Forms;
using System.Collections;
using System.IO;
using System.Drawing;
using System.Drawing.Imaging;
using System.Net;
using System.Web;
using System.Reflection;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Runtime;
using System.Text;
using System.Diagnostics;
using System.Net.Sockets;
using System.Threading;
using AcedUtils;
using BrainTechLLC.Communications;

// This code file contains functions written by Owen Emlen for use
// by Owen Emlen, BrainTech LLC, and contract clients.  Functions used
// within this code file do not become the exclusive property of any
// one contracting client (i.e. I have, and will continue to use these 
// general functions in projects with other clients).  However, with that said, 
// clients may modify and use this code as they see fit.  Although not 
// legally enforcable, I would appreciate if clients refrain from 
// copying/pasting this code into applications other than the primary project 
// for which my work was contracted.  If you do end up using these functions 
// in other applications, I would appreciate a brief mention in a comment 
// somewhere within the code file, along with contact information so that 
// those who appreciate the code might contact me regarding future 
// programming contracts or services.  Thank you!
// Owen Emlen, owene_1998@yahoo.com, http://www.braintechllc.com/owen.aspx
namespace BrainTechLLC.Communications
{
  public enum MessageTypes : short
  {
    None = 0,
    ScreenUpdatePacket = 1,
    HostResolutionChange = 2,
    CommandExecutionPacket = 3
  }

  /// <summary>
  /// Represents and handles a single communication connection
  /// This may be a listener (for TCP/IP) or outgoing connection, or even an open-on-demand web connection
  /// </summary>
  [Browsable(true)]
  public sealed class CommConnection : IDisposable
  {
    public const int nPacketHeaderSize = 14;
    public const int nPacketLengthPrefixSize = 4;
    public ICommunications commInterfaceToUse = null;

    /// <summary>
    /// Clean up any TCP/IP connections and memory streams, if necessary
    /// </summary>
    public void Dispose()
    {
      if (descriptor != null)
      {
        descriptor.Close();
        descriptor = null;
      }
      if (msPartialRecv != null)
      {
        msPartialRecv.Close();
        msPartialRecv = null;
      }
    }

    // Constants.  No need to adjust unless you are running on a highly-stressed cpu

    /// <summary>
    /// nMessageQOut controls how many outgoing messages can be queue'd before
    /// new ones are dropped.  If you set this number very high, long delays
    /// could appear between what's going on locally and what the remote machine
    /// is seeing.  If you set this to a very small value (1 or 0), then things
    /// will tend to "jump around" more (i.e. mouse movements become less smooth)
    /// </summary>
    private int nMessageQOut = 1; 

    public int MessageQOut
    {
      get { return nMessageQOut; }
      set { nMessageQOut = value; }
    }

    /// <summary>
    /// Give threads some slip time before they try to start up or do anything
    /// </summary>
    public const int nSleepProcessIncomingDataThreadStart = 400;
    public const int nSleepSendOutgoingDataThreadStart = 1000;
    public const int nSleepReceiveIncomingDataThreadStart = 500;
    public const int nSleepAcceptIncomingConnectionsInteration = 300;
    public const int nSleepConnectionsCleanupStart = 500;

    private int nMaxImageQueue = 10;

    public int MaxImageQueue
    {
      get { return nMaxImageQueue; }
      set { nMaxImageQueue = value; }
    }

    // Playback settings
    public static double dPlaybackSpeedMultiplier = 1.0;
    private DateTime dtRecorded = DateTime.MinValue;
    private DateTime dtPlayback = DateTime.MinValue;

    /// <summary>
    /// If no full incoming data packets were received (thus, a message
    /// cannot be processed yet), then sleep for this amount of time (in ms).
    /// This should be fairly long... 80-120ms, to give a chance for new
    /// data to come in.  If it's too short, cpu usage will skyrocket as the
    /// thread slowly appends a trickle of incoming data and re-checks for
    /// message completion
    /// </summary>
    public const int nSleepProcessIncomingDataThreadNoActivity = 150;
    /// <summary>
    /// Take a breather (in ms) after sending a chunk of outgoing data
    /// (a chunk may contain multiple packets)
    /// </summary>
    public const int nSleepSendOutgoingDataThreadIteration = 40; // was 40
    /// <summary>
    /// Take a breather (in ms) after checking for incoming data.
    /// We don't want this to be too short or we will keep pulling in
    /// tiny bits of data and putting them in packets that need to be
    /// concatenated eventually.  However, if we make this too long, then
    /// the application is less responsive when incoming data is received
    /// </summary>
    public const int nSleepReceiveIncomingDataThreadIteration = 55;

    public static int nSleepReceiveIncomingDataThreadIterationForHTTP = 70;

    /// <summary>
    /// If this is a listener, this sets the maximum simultaneous 
    /// PENDING connections from remote hosts before new connections are refused.
    /// Note: this does NOT limit the number of connections.  New connections
    /// are picked up from the pending queue approximately every 500ms.
    /// </summary>
    public const int nMaxSimultaneousIncomingConnections = 4;

    /// <summary>
    /// Message prioritization (used primarily for audio/video packets to control audio delay, etc)
    /// </summary>
    public enum eMessagePriority
    {
      Lowest,
      Low,
      Normal,
      AboveNormal,
      High
    }

    public CommConnection(System.Net.IPAddress listenOn, int nPort)
    {
      ListenOnLocalIP = listenOn;
      Port = nPort;
      rgbmIncoming = Queue.Synchronized(bmInC);
      rgmsIncomingCommands = Queue.Synchronized(msInCCmd);
      qCommandsToExecute = Queue.Synchronized(qCmdsToExecute);
      commInterfaceToUse = null;
    }

    private void InitializeNewInstance(CommConnection parentListener, ICommunications commInterface)
    {
      rgbmIncoming = Queue.Synchronized(bmInC);
      rgmsIncomingCommands = Queue.Synchronized(msInCCmd);
      rgmsAudio = Queue.Synchronized(msInCAud);
      qCommandsToExecute = Queue.Synchronized(qCmdsToExecute);
      commInterfaceToUse = commInterface;
      this.parentListener = parentListener;
      commInterfaceToUse = commInterface;
      parentListener.rgDescriptors.Add(this);
    }

    public CommConnection(CommConnection parentListener, ICommunications commInterface) { InitializeNewInstance(parentListener, commInterface); }

    public CommConnection(CommConnection parentListener, bool fConnectOnDemand, ICommunications commInterface)
    {
      this.fConnectionOpenOnDemand = fConnectOnDemand;
      InitializeNewInstance(parentListener, commInterface);
    }

    /// <summary>
    /// If true, then the communications layer is expected to handle transmit/receive on demand (i.e.
    /// a "connectionless connection").  If false, the connection is expected to stay open 
    /// in order to send/receive data (i.e. TCP/IP)
    /// </summary>
    public bool fConnectionOpenOnDemand = false;
    public bool fNewConnection = false;
    private static byte[] videoHeader = null;
    private static byte[] commandHeader = null;
    private static byte[] audioHeader = null;
    private CommConnection parentListener = null;
    private long nSent = 0;
    private long nRecv = 0;
    private System.Net.IPAddress ipLocal = System.Net.IPAddress.Any;
    private TcpListener main_control = null;
    private int port = 4006;
    private bool fStop = false;
    private Thread thrdListn;
    private Thread thrdSnd;
    private Thread thrdRcv;
    private Thread thrdProcess;
    private TcpClient descriptor = null;
    private bool fNeedsClosing = false;
    private Queue bmInC = new Queue(8);
    private Queue msInCAud = new Queue(8);
    private Queue msInCCmd = new Queue(8);
    private int fProcessing = 0;
    public MemoryStream msPartialRecv = null;
    private bool fIncomingConnection = false;
    private Queue qCmdsToExecute = new Queue(4);
    public Queue qCommandsToExecute;

    /// <summary>
    /// Stores "chunks" of pending incoming and outgoing bytes
    /// </summary>
    public Queue<byte[]> rgOutgoingBytes = new Queue<byte[]>(48);
    public Queue<byte[]> rgIncomingBytes = new Queue<byte[]>(48);
    /// <summary>
    /// Synchronized queue of incoming bitmap images
    /// </summary>
    public Queue rgbmIncoming;
    /// <summary>
    /// Synchronized queue of incoming command data
    /// </summary>
    public Queue rgmsIncomingCommands;

    public Queue rgmsAudio;

    /// <summary>
    /// A count of the number of packets already being sent to this remote host
    /// </summary>
    public int nAlreadySending = 0;
    /// <summary>
    /// Is this connection an incoming connection (established from a remote machine?)
    /// </summary>
    public bool IsIncomingConnection { get { return fIncomingConnection; } }
    /// <summary>
    /// Total number of bytes sent out on this connection
    /// </summary>
    public long BytesSent { get { return nSent; } }
    /// <summary>
    /// Total number of bytes received from this connection
    /// </summary>
    public long BytesReceived { get { return nRecv; } }
    /// <summary>
    /// If this is a listener, the ListenOnLocalIP sets or retrieves the
    /// local IP address on which this socket should listen
    /// </summary>
    public IPAddress ListenOnLocalIP { get { return ipLocal; } set { ipLocal = value; } }
    /// <summary>
    /// rgDescriptors contains a list of all current connections
    /// </summary>
    public List<CommConnection> rgDescriptors = new List<CommConnection>(2);
    /// <summary>
    /// Gets or sets the desired connect-to port (for outgoing connections)
    /// or the listening port if this is a listener
    /// </summary>
    public int Port { get { return port; } set { port = value; } }
    /// <summary>
    /// Should the listening, processing, receiving, and sending threads
    /// terminate?
    /// </summary>
    public bool Stop { get { return fStop; } set { fStop = value; } }
    /// <summary>
    /// Does this specific connection need closing on the next cleanup pass?
    /// </summary>
    public bool NeedsClosing { get { return fNeedsClosing; } set { fNeedsClosing = value; } }
    /// <summary>
    /// The actual TCPClient associated with this connection
    /// </summary>
    public TcpClient Descriptor { get { return descriptor; } set { descriptor = value; } }
    /// <summary>
    /// Is this connection currently processing incoming data?
    /// </summary>
    public int IsProcessing { get { return fProcessing; } set { fProcessing = value; } }

    /// <summary>
    /// Creates a transmissable packet with a given packet identifier (for type of
    /// information being transmitted), a packed finished identifier, and the
    /// data to be transmitted
    /// </summary>
    /// <param name="packetID">A byte array identifying the beginning of a packet</param>
    /// <param name="packetFinishedID">A byte array identifying the end of the packet</param>
    /// <param name="byteData">The data to be transmitted</param>
    /// <param name="nDataLen">The length of data to be transmitted</param>
    /// <returns></returns>
    public static byte[] CreateCommandPacket(byte[] byteData, int nDataLen)
    {
      CheckCommandHeader();
      byte[] rgOut = null;
      using (MemoryStream msOut = new MemoryStream(
        nDataLen + nPacketHeaderSize + nPacketLengthPrefixSize)
        )
      {
        using (BinaryWriter bw = new BinaryWriter(msOut))
        {
          bw.Write((int)(nDataLen + nPacketHeaderSize));
          bw.Write(commandHeader, 0, nPacketHeaderSize);
          bw.Write(byteData, 0, nDataLen);
          bw.Flush();
          rgOut = msOut.ToArray();
          bw.Close();
        }
      }
      return rgOut;
    }

    public static byte[] CreateAudioPacket(byte[] byteData, int nDataLen)
    {
      byte[] rgOut = null;
      CheckAudioHeader();
      try
      {
        using (MemoryStream msOut = new MemoryStream(
          nDataLen + nPacketHeaderSize + nPacketLengthPrefixSize)
          )
        {
          using (BinaryWriter bw = new BinaryWriter(msOut))
          {
            bw.Write((int)(nDataLen + nPacketHeaderSize));
            bw.Write(audioHeader, 0, nPacketHeaderSize);
            bw.Write(byteData, 0, nDataLen);
            bw.Flush();
            rgOut = msOut.ToArray();
            bw.Close();
          }
        }
      }
      catch { }
      return rgOut;
    }
    /// <summary>
    /// Sets up the command header
    /// </summary>
    public static void CheckCommandHeader()
    {
      if (commandHeader == null)
      {
        commandHeader = new byte[nPacketHeaderSize];
        int nByteIdx = 0;
        for (int i = 28; i < 56; i += 2, nByteIdx++) { commandHeader[nByteIdx] = (byte)(i + 1); }
      }
    }

    /// <summary>
    /// Sets up the command header
    /// </summary>
    public static void CheckAudioHeader()
    {
      if (audioHeader == null)
      {
        audioHeader = new byte[nPacketHeaderSize];
        int nByteIdx = 0;
        for (int i = 56; i < 84; i += 2, nByteIdx++) { audioHeader[nByteIdx] = (byte)(i + 1); }
      }
    }

    /// <summary>
    /// Sets up the Video Header
    /// </summary>
    public static void CheckVideoHeader()
    {
      if (videoHeader == null)
      {
        videoHeader = new byte[nPacketHeaderSize];
        int nByteIdx = 0;
        for (int i = 0; i < 14; i++) { videoHeader[nByteIdx++] = (byte)(i + 1); }
      }
    }

    /// <summary>
    /// Convert an image to a byte array using a specified codec and encoding parameters
    /// </summary>
    /// <param name="img"></param>
    /// <param name="codecinfo"></param>
    /// <param name="encoderParams"></param>
    /// <param name="nPacketSize"></param>
    /// <returns></returns>
    public static byte[] ImageToBytes(Image img, ImageCodecInfo codecinfo, EncoderParameters encoderParams, ref int nPacketSize)
    {
      // Make sure the header is initialized
      CheckVideoHeader();
      if (img == null) return null;
      // create a small memory stream
      MemoryStream ms = new MemoryStream(32000);
      // begin at byte 4.  We will insert the image+header length into the first 4 bytes after we know how many
      // bytes are required to store the serialized image
      ms.Seek(4, SeekOrigin.Begin);
      // write the video header starting at byte 4
      ms.Write(videoHeader, 0, nPacketHeaderSize);
      // serialize the image using the specified codec and encoding parameters
      img.Save(ms, codecinfo, encoderParams);
      ms.Seek(0, SeekOrigin.Begin);
      // Calculate the length of the image plus its header
      int nLen = (int)(ms.Length) - 4;
      BinaryWriter bw = new BinaryWriter(ms);
      // Go back and fill in this length at position 0 (4 bytes)
      bw.Write((int)nLen);
      bw.Flush();
      // Copy the memory stream into a fixed-size byte buffer
      byte[] rgBytes = new byte[nLen + nPacketLengthPrefixSize];
      ms.Seek(0, SeekOrigin.Begin);
      Array.Copy(ms.GetBuffer(), rgBytes, nLen + nPacketLengthPrefixSize);
      bw.Close();
      ms.Close();
      // return the packet size to the caller in a ref parameter
      nPacketSize = nLen + nPacketLengthPrefixSize;
      return rgBytes;
    }

    /// <summary>
    /// Sets up this CommCommunication class as a listener, and begins
    /// listening for incoming connections on the specified port
    /// </summary>
    public void BeginListening()
    {
      //MessageBox.Show("Began listening on " + ipLocal.Address.ToString() + " port " + port.ToString());
      ThreadStart ts1 = new ThreadStart(JustListen);
      thrdListn = new Thread(ts1);
      thrdListn.Priority = ThreadPriority.Normal;
      thrdListn.IsBackground = true;
      thrdListn.TrySetApartmentState(ApartmentState.MTA);
      thrdListn.Start();
    }

    /// <summary>
    /// Begins the send and receive threads
    /// </summary>
    public void BeginSendReceiveThreads()
    {
      ThreadStart ts2 = new ThreadStart(ReceiveIncoming);
      thrdRcv = new Thread(ts2);
      thrdRcv.Priority = ThreadPriority.Normal;
      thrdRcv.IsBackground = true;
      thrdRcv.TrySetApartmentState(ApartmentState.MTA);
      thrdRcv.Start();

      ThreadStart ts3 = new ThreadStart(SendOutgoing);
      thrdSnd = new Thread(ts3);
      thrdSnd.Priority = ThreadPriority.AboveNormal;
      thrdSnd.IsBackground = true;
      thrdSnd.TrySetApartmentState(ApartmentState.MTA);
      thrdSnd.Start();
    }

    /// <summary>
    /// Begins the processing thread
    /// </summary>
    public void BeginProcessingThread()
    {
      ThreadStart ts4 = new ThreadStart(ProcessIncomingData);
      thrdProcess = new Thread(ts4);
      thrdProcess.Priority = ThreadPriority.Normal;
      thrdProcess.IsBackground = true;
      thrdProcess.TrySetApartmentState(ApartmentState.MTA);
      thrdProcess.Start();
    }

    /// <summary>
    /// Broadcast a message out (to all connected clients) with a given priority
    /// </summary>
    /// <param name="rgMessage">The message to be broadcast</param>
    /// <param name="nPriority">The priority of the message</param>
    /// <returns>The number of clients the message was successfully sent to</returns>
    public int BroadcastOut(byte[] rgMessage, int nLen, eMessagePriority nPriority)
    {
      int nSentTo = 0;
      int nCountDescs = rgDescriptors.Count;
      for (int n = 0; n < nCountDescs; n++)
      {
        CommConnection dd = null;
        try { dd = (CommConnection)rgDescriptors[n]; }
        catch { continue; }
        if (fConnectionOpenOnDemand ||
          ( dd.descriptor != null &&
            dd.NeedsClosing == false && dd.descriptor.Client != null &&
            dd.descriptor.Client.Connected == true))
        {
          if (dd.SendMessageOut(rgMessage, nLen, nPriority) == true) nSentTo++;
          if (fConnectionOpenOnDemand) break;
        }
      }

      return nSentTo;
    }

    /// <summary>
    /// Entry point for the thread that processes incoming data streams from
    /// all connections
    /// </summary>
    private void ProcessIncomingData()
    {
      Thread.Sleep(nSleepProcessIncomingDataThreadStart);
      int nProcessed;
      while (fStop == false)
      {
        int nIncoming = 0;
        nProcessed = 0;
        int nCountDescs = rgDescriptors.Count;
        while (nCountDescs == 0 && !fStop)
        {
          Thread.Sleep(300);
          nCountDescs = rgDescriptors.Count;
        }
        for (int nCurrentlyWorking = 0; nCurrentlyWorking < nCountDescs; nCurrentlyWorking++)
        {
          CommConnection dd = null;
          try { dd = (CommConnection)rgDescriptors[nCurrentlyWorking]; }
          catch { continue; }

          bool fPlayback = false;
          while (dd.commInterfaceToUse != null && dd.commInterfaceToUse.PlayingBackData)
          {
            if (!fPlayback)
            {
              fPlayback = true;
              dtRecorded = DateTime.MinValue;
              dtPlayback = DateTime.MinValue;
            }
            byte[] rgPlaybackData = dd.commInterfaceToUse.GetPlaybackData();
            if (rgPlaybackData != null)
            {
              try
              {
                lock (rgIncomingBytes)
                {
                  dd.rgIncomingBytes.Clear();
                  dd.msPartialRecv.SetLength(0);
                }
              }
              catch { }

              dd.commInterfaceToUse.EnqueuePlaybackData(rgPlaybackData);

              dd.fProcessing = 1;
              try
              {
                while (true)
                {
                  while (dd.GetNextFullMessage(true)) { Thread.Sleep(10); }
                  rgPlaybackData = dd.commInterfaceToUse.GetPlaybackData();
                  if (rgPlaybackData == null) break;
                  dd.commInterfaceToUse.EnqueuePlaybackData(rgPlaybackData);
                }

              }
              catch (Exception ex)
              {
                Debug.WriteLine(ex.ToString());
              }
              dd.fProcessing = 0;
            }
            Thread.Sleep(20);
          }

          if (
            (dd.fConnectionOpenOnDemand ||
            (dd != null && dd.NeedsClosing == false &&
              dd.descriptor != null &&
              dd.descriptor.Client.Connected == true &&
              dd.fProcessing == 0)))
          {
            nIncoming = dd.rgIncomingBytes.Count;
            if (nIncoming > 0)
            {
              dd.fProcessing = 1;
              if (dd.GetNextFullMessage(false))
              {
                nProcessed++;
                if (dd.fConnectionOpenOnDemand) Thread.Sleep(20);
                else { while (dd.GetNextFullMessage(false)) { nProcessed++; } }
              }
              dd.fProcessing = 0;
            }
          }
        }

        if (nProcessed == 0)
        {
          Thread.Sleep(nSleepProcessIncomingDataThreadNoActivity);
        }
      }
    }

    /// <summary>
    /// Entry point for the thread that sends all pending outgoing data to
    /// all connected clients
    /// </summary>
    private void SendOutgoing()
    {
      Thread.Sleep(nSleepSendOutgoingDataThreadStart);
      int nIdleLoops = 0;
      while (fStop == false)
      {
        CommConnection dd = null;
        int nSentDataTo = 0;
        for (int i = 0; i < rgDescriptors.Count; i++)
        {
          try
          {
            dd = (CommConnection)rgDescriptors[i];
          }
          catch { continue; }

          if (fConnectionOpenOnDemand && dd.commInterfaceToUse != null)
          {
            if (commInterfaceToUse == null) commInterfaceToUse = dd.commInterfaceToUse;
            // If this communications library is playing back data, sleep and wait for it to finish            
            while (dd.commInterfaceToUse.PlayingBackData)
            {
              Thread.Sleep(300);
            }
            while (SendBytesOutgoing() > 0)
            {
              nSentDataTo++;
              nIdleLoops = 0;
              Thread.Sleep(20);
            }
          }
          else if (dd.Descriptor != null &&
                   dd.descriptor.Client.Connected == true)
          {
            while (dd.SendBytesOutgoing() > 0)
            {
              nSentDataTo++;
              nIdleLoops = 0;
            }
          }
        }

        if (nSentDataTo == 0)
        {
          // Take a break.  There was nothing to send.
          Thread.Sleep(50);
          nIdleLoops++;
          if (nIdleLoops > 300) { Thread.Sleep(300); }
          else if (nIdleLoops > 100) { Thread.Sleep(200); }
          else if (nIdleLoops > 5) { Thread.Sleep(10 + ((3 + nIdleLoops) * 2)); }
        }
        else
        {
          Thread.Sleep(nSleepSendOutgoingDataThreadIteration);
        }
      }
    }

    /// <summary>
    /// Entry point for the main thread that checks all connections for incoming data
    /// </summary>
    private void ReceiveIncoming()
    {
      Thread.Sleep(nSleepReceiveIncomingDataThreadStart);
      int nIdleLoops = 0;

      while (fStop == false)
      {
        int nRecvCount = 0;
        int nDescs = rgDescriptors.Count;

        for (int nCurr = 0; nCurr < nDescs; nCurr++)
        {
          CommConnection dd = null;
          try
          {
            dd = (CommConnection)rgDescriptors[nCurr];
          }
          catch
          {
            continue;
          }

          while (dd.commInterfaceToUse != null && dd.commInterfaceToUse.PlayingBackData)
          {
            Thread.Sleep(400);
          }

          if (dd.fConnectionOpenOnDemand ||
            (dd.descriptor != null &&
             dd.NeedsClosing == false &&
             dd.descriptor.Connected == true))
          {
          TryAgain:
            int nBytesIncoming = dd.CheckBytesIncoming();
            if (nBytesIncoming > 0)
            {
              nRecvCount++;
              nIdleLoops = 0;
              if (dd.fConnectionOpenOnDemand == false)
              {
                while (nBytesIncoming > 64000)
                {
                  nBytesIncoming = dd.CheckBytesIncoming();
                  nRecvCount++;
                }
              }
            }
            if (dd.fConnectionOpenOnDemand)
            {
              Thread.Sleep(nSleepReceiveIncomingDataThreadIterationForHTTP);
              if (nRecvCount > 0)
              {
                goto TryAgain;
              }
            }
          }
        }

        if (nRecvCount == 0)
        {
          // No data was available this round.  Take a breather
          Thread.Sleep(120);
          nIdleLoops++;
          if (nIdleLoops > 500) { Thread.Sleep(300); }
          else if (nIdleLoops > 100) { Thread.Sleep(150); }
          else if (nIdleLoops > 5) { Thread.Sleep(10 + (nIdleLoops * 2)); }
        }
      }
    }

    /// <summary>
    /// Just stay cool and listen for incoming connections on a given port
    /// Occasionally clean up connections that need to be closed
    /// </summary>
    private void JustListen()
    {
      main_control = new TcpListener(ipLocal, port);
      if (ipLocal != IPAddress.None)
      {
        try
        {
          main_control.Start(4);
        }
        catch (Exception ex)
        {
          Debug.WriteLine("Excepting trying to begin listening for incoming connections: " + ex.ToString());
          ipLocal = IPAddress.None;
        }
      }

      while (fStop == false)
      {
        Thread.Sleep(nSleepAcceptIncomingConnectionsInteration);

        if (ipLocal != IPAddress.None)
        {
          if (main_control.Pending() == true)
          {
            CommConnection dd = null;
            try
            {
              TcpClient cli = main_control.AcceptTcpClient();
              cli.Client.NoDelay = false;
              cli.Client.ReceiveBufferSize = 8192 * 2;
              cli.Client.SendBufferSize = 8192 * 2;
              cli.Client.Blocking = true;
              dd = new CommConnection(this, null);  // this.commConnectionToUse?
              dd.fIncomingConnection = true;
              dd.Descriptor = cli;
              dd.fNewConnection = true;
              fNewConnection = true;
            }
            catch (Exception ex) { Debug.WriteLine("Pending Connection: " + ex.ToString()); }
          }
        }

        Thread.Sleep(nSleepConnectionsCleanupStart);

        // Do a quick cleanup pass
        // Monitor.Enter(rgDescriptors);
        for (int i = rgDescriptors.Count - 1; i >= 0; i--)
        {
          CommConnection dd = (CommConnection)rgDescriptors[i];
          if (dd.NeedsClosing == true && dd.Descriptor != null && dd.fProcessing == 0)
          {
            if (dd.Descriptor.Connected == true)
            {
              try { dd.Descriptor.Client.Shutdown(SocketShutdown.Both); }
              catch { }
              try { dd.Descriptor.Client.Close(); }
              catch { }
              try { dd.Descriptor.Close(); }
              catch { }
            }
            dd.Descriptor = null;
            rgDescriptors.RemoveAt(i);
          }
        }
        // Monitor.Exit(rgDescriptors);
        Thread.Sleep(nSleepAcceptIncomingConnectionsInteration);
      }
      if (ipLocal != IPAddress.None) { main_control.Stop(); }
    }

    /// <summary>
    /// Attempts to connect to a specific port on a specific host
    /// </summary>
    /// <param name="szHost">The remote host</param>
    /// <param name="nPort">The remote port</param>
    /// <returns>True, otherwise an exception is thrown.  InnerException will
    /// contain the actual connection error returned by the TcpClient.Connect
    /// method call</returns>
    public bool Connect(string szHost, int nPort)
    {
      descriptor = new TcpClient();
      try { descriptor.Connect(szHost, nPort); }
      catch (Exception ex) { throw new Exception("Error trying to connect to " + szHost + " on port " + nPort.ToString(), ex); }
      return true;
    }

    private void EnqueueBitmapIfRoom(Bitmap bm)
    {
      if (bm != null)
      {
        if (rgbmIncoming.Count > nMaxImageQueue) { rgbmIncoming.Clear(); }
        rgbmIncoming.Enqueue(bm);
      }
    }

    /// <summary>
    /// Extract an image (bitmap) from compressed bytes (usually a jpeg)
    /// </summary>
    /// <param name="rgBytes">The bytes containing the image information</param>
    /// <returns>The reconstructed bitmap</returns>
    public Bitmap ExtractImageFromBytes(byte[] rgBytes)
    {
      Bitmap img = null;
      MemoryStream ms = new MemoryStream(rgBytes);
      ms.Seek(0, SeekOrigin.Begin);
      try { img = (Bitmap)Image.FromStream(ms); }
      catch { }
      return img;
    }

    /// <summary>
    /// Retrieves the last fully received image.
    /// </summary>
    /// <param name="dFrameTime">The time the image was received, in local ticks</param>
    /// <returns>Null if there is no image waiting, otherwise, a bitmap</returns>
    public Bitmap LastFrame(out double dFrameTime)
    {
      Bitmap bmToReturn = null;
      if (rgbmIncoming.Count > 0)
      {
        try
        {
          bmToReturn = (Bitmap)rgbmIncoming.Dequeue();
          dFrameTime = DateTime.Now.Ticks;
        }
        catch { dFrameTime = 0.0; }
      }
      else
      {
        dFrameTime = 0.0;
      }
      return bmToReturn;
    }

    /// <summary>
    /// Retrieves the next full message from the partial receive buffer
    /// </summary>
    /// <returns>True if a message was waiting, false if no complete message
    /// was found in the incoming buffer</returns>
    public bool GetNextFullMessage(bool fPlaybackStream)
    {
      byte[] rgNext = null;
      if (msPartialRecv == null) msPartialRecv = new MemoryStream(64000);

      if (rgIncomingBytes.Count > 0)
      {
        msPartialRecv.Seek(0, SeekOrigin.End);
        lock (rgIncomingBytes)
        {
          // re-check of incoming byte count necessary to avoid premature locking
          while (rgIncomingBytes.Count > 0)
          {
            rgNext = rgIncomingBytes.Dequeue();
            msPartialRecv.Write(rgNext, 0, rgNext.Length);
          }
        }
      }
      else
      {
        return false;
      }

      CheckVideoHeader();
      CheckAudioHeader();
      CheckCommandHeader();
      bool fFullCommandReceived = false;
      int nTotalLength = (int)msPartialRecv.Length;
      msPartialRecv.Seek(0, SeekOrigin.Begin);
      BinaryReader br = new BinaryReader(msPartialRecv);

      while (nTotalLength - (int)br.BaseStream.Position > nPacketLengthPrefixSize)
      {
        if (fPlaybackStream)
        {
          // If playing back data from a recorded stream, the first 8 bytes contain
          // a date/time stamp. This is critical to give the impression of playback happening in
          // "real time" - i.e. pauses, etc
          DateTime dtActionRecorded = DateTime.FromBinary(br.ReadInt64());
          if (dtRecorded == DateTime.MinValue)
          {
            dtRecorded = dtActionRecorded;
            dtPlayback = DateTime.Now;
          }
          else
          {
            // Synchronize playback to actual time
            DateTime dtNow = DateTime.Now;
            TimeSpan tsTimeElapsedLocal = dtNow.Subtract(dtPlayback);
            double dLocal = tsTimeElapsedLocal.TotalMilliseconds * dPlaybackSpeedMultiplier;
            TimeSpan tsTimeElapsedRemote = dtActionRecorded.Subtract(dtRecorded);
            double dRemote = tsTimeElapsedRemote.TotalMilliseconds;
            while (dLocal < dRemote)
            {
              // If the time elapsed locally is less than the time elapsed between updates in the playback
              // stream, then take a break and check again ~30ms from now
              Thread.Sleep(30);
              dtNow = DateTime.Now;
              tsTimeElapsedLocal = dtNow.Subtract(dtPlayback);
              dLocal = tsTimeElapsedLocal.TotalMilliseconds * dPlaybackSpeedMultiplier;
            }
            dtRecorded = dtActionRecorded;
            dtPlayback = dtNow;
          }
        }

        int nBytesExpected = (int)br.ReadInt32();
        if (nBytesExpected > 5000000 || nBytesExpected < 0)
        {
          // Not a sensible value.  There should not be 5MB+ or negative number of bytes waiting
          // Try disaster recovery here.
          msPartialRecv.SetLength(0);
          lock (rgIncomingBytes)
          {
            rgIncomingBytes.Clear();
          }
          Debug.WriteLine("Error: expected " + nBytesExpected.ToString() + " bytes.  Value does not seem reasonable - clearing receive buffer.");
          return false;
          //MessageBox.Show("Crap");
          //Debug.Assert(false);
        }
        if ((int)br.BaseStream.Position + nBytesExpected > nTotalLength)
        {
          // If the message is not complete (i.e. data may be coming in on the next incoming message),
          // then rewind the stream back to the expected data length (from above, when we 
          // read in the expected data length)
          if (br.BaseStream.Position != 0)
          {
            br.BaseStream.Seek(-nPacketLengthPrefixSize, SeekOrigin.Current);
            if (fPlaybackStream)
            {
              // Playback streams also have a date/time stamp that we read.  Rewind 8 bytes further.
              br.BaseStream.Seek(-8, SeekOrigin.Current);
            }
          }
          break;
        }
        byte[] rgMessage = br.ReadBytes(nBytesExpected);

        // Do something with rgMessage -- it contains goodies! (i.e. it contains a complete message)        
        if (rgMessage[0] == videoHeader[0] && rgMessage[1] == videoHeader[1] &&
            rgMessage[2] == videoHeader[2] && rgMessage[3] == videoHeader[3] &&
            rgMessage[4] == videoHeader[4] && rgMessage[5] == videoHeader[5] &&
            rgMessage[6] == videoHeader[6] && rgMessage[7] == videoHeader[7] &&
            rgMessage[8] == videoHeader[8] && rgMessage[9] == videoHeader[9] &&
            rgMessage[10] == videoHeader[10] && rgMessage[11] == videoHeader[11] &&
            rgMessage[12] == videoHeader[12] && rgMessage[13] == videoHeader[13])
        {
          int nLength = nBytesExpected - nPacketHeaderSize;
          byte[] rgBM = new byte[nLength + 256];
          Array.Copy(rgMessage, nPacketHeaderSize, rgBM, 0, nLength);
          Bitmap bm = ExtractImageFromBytes(rgBM);
          EnqueueBitmapIfRoom(bm);
          fFullCommandReceived = true;
          if (commInterfaceToUse != null)
          {
            commInterfaceToUse.WriteIncomingData(nBytesExpected, rgMessage, nLength + nPacketHeaderSize);
          }
        }
        else if (
          rgMessage[0] == commandHeader[0] && rgMessage[1] == commandHeader[1] &&
          rgMessage[2] == commandHeader[2] && rgMessage[3] == commandHeader[3] &&
          rgMessage[4] == commandHeader[4] && rgMessage[5] == commandHeader[5] &&
          rgMessage[6] == commandHeader[6] && rgMessage[7] == commandHeader[7] &&
          rgMessage[8] == commandHeader[8] && rgMessage[9] == commandHeader[9] &&
          rgMessage[10] == commandHeader[10] && rgMessage[11] == commandHeader[11] &&
          rgMessage[12] == commandHeader[12] && rgMessage[13] == commandHeader[13])
        {
          int nLength = nBytesExpected - nPacketHeaderSize;
          MemoryStream msCommand = new MemoryStream(nLength + 64);
          msCommand.Write(rgMessage, nPacketHeaderSize, nLength);
          msCommand.Seek(0, SeekOrigin.Begin);
          rgmsIncomingCommands.Enqueue(msCommand);
          fFullCommandReceived = true;
          if (commInterfaceToUse != null)
          {
            commInterfaceToUse.WriteIncomingData(nBytesExpected, rgMessage, nLength + nPacketHeaderSize);
          }
        }
        else if (
     rgMessage[0] == audioHeader[0] && rgMessage[1] == audioHeader[1] &&
     rgMessage[2] == audioHeader[2] && rgMessage[3] == audioHeader[3] &&
     rgMessage[4] == audioHeader[4] && rgMessage[5] == audioHeader[5] &&
     rgMessage[6] == audioHeader[6] && rgMessage[7] == audioHeader[7] &&
     rgMessage[8] == audioHeader[8] && rgMessage[9] == audioHeader[9] &&
     rgMessage[10] == audioHeader[10] && rgMessage[11] == audioHeader[11] &&
     rgMessage[12] == audioHeader[12] && rgMessage[13] == audioHeader[13])
        {
          int nLength = nBytesExpected - nPacketHeaderSize;
          MemoryStream msCommand = new MemoryStream(nLength + 64);
          msCommand.Write(rgMessage, nPacketHeaderSize, nLength);
          msCommand.Seek(0, SeekOrigin.Begin);
          rgmsAudio.Enqueue(msCommand);
          fFullCommandReceived = true;
        }
        else
        {
          Debug.Assert(false, "Unknown transmission type");
        }
      }

      if ((int)br.BaseStream.Position == 0)
      {
        // If we didn't end up going anywhere (i.e. the message was not complete), then
        // make sure the partial receive memory stream is set to append incoming messages to its tail
        msPartialRecv.Seek(0, SeekOrigin.End);
      }
      else if ((int)br.BaseStream.Position < nTotalLength)
      {
        // If we read and used some bytes from the stream (but not the whole stream), then
        // we need to update the partial receive memory stream to reflect this
        int nRemain = nTotalLength - (int)br.BaseStream.Position;
        byte[] rgRemainingBytes = br.ReadBytes(nRemain);
        msPartialRecv = new MemoryStream(nRemain + 16384);
        msPartialRecv.Write(rgRemainingBytes, 0, nRemain);
      }
      else
      {
        // Otherwise all bytes were read and used.  Clear the partial receive memory stream
        msPartialRecv.SetLength(0);
        msPartialRecv.Seek(0, SeekOrigin.Begin);
      }

      return fFullCommandReceived;
    }


    public bool SendMessageOut(byte[] rgMessage, int nLen, eMessagePriority nPriority)
    {
      int nOutgoing = rgOutgoingBytes.Count;
      if (nPriority == eMessagePriority.Lowest && nOutgoing > nMessageQOut) return false;
      else if (nPriority == eMessagePriority.Low && nOutgoing > 1 + nMessageQOut) return false;
      else if (nPriority == eMessagePriority.Normal && nOutgoing > 2 + nMessageQOut) return false;
      else if (nPriority == eMessagePriority.AboveNormal && nOutgoing > 3 + nMessageQOut) return false;
      else if (nPriority == eMessagePriority.High && nOutgoing > 4 + nMessageQOut) return false;
      EnqueueForSend(rgMessage);
      return true;
    }

    /// <summary>
    /// Sends some queue'd (waiting) packets to the current connection
    /// </summary>
    /// <returns>Returns 1 on success, 0 on failure</returns>
    public int SendBytesOutgoing()
    {
      byte[] packet1 = null;
      int nP1Len = 0;
      if (rgOutgoingBytes.Count > 0)
      {
        lock (rgOutgoingBytes)
        {
          if (rgOutgoingBytes.Count > 0)
          {
            packet1 = rgOutgoingBytes.Dequeue();
            nP1Len = packet1.Length;
            while (nP1Len < 15000 && rgOutgoingBytes.Count > 0)
            {
              byte[] packet2 = rgOutgoingBytes.Dequeue();
              int nP2Len = packet2.Length;
              MemoryStream ms = new MemoryStream(nP1Len + nP2Len);
              ms.Write(packet1, 0, nP1Len);
              ms.Write(packet2, 0, nP2Len);
              packet1 = ms.ToArray();
              nP1Len += nP2Len;
              ms.Close();
            }
          }
        }
      }

      if (packet1 != null)
      {
        int nLenSending = nP1Len;

        if (fConnectionOpenOnDemand && commInterfaceToUse != null)
        {
          if (commInterfaceToUse.BroadcastDataOutDirect(packet1, nLenSending) == CommunicationsResponse.Success)
          {
            AddBytesSent(nLenSending);
            return 1;
          }
          else { return 0; }
        }

        if (descriptor == null) { return 0; }
        SocketError se;
        Interlocked.Increment(ref nAlreadySending);
        int nnn = descriptor.Client.Send(packet1, 0, nLenSending, SocketFlags.None, out se);
        if (nnn != nLenSending) { return 0; }

        if (se == SocketError.Success)
        {          
          AddBytesSent(nLenSending);
          parentListener.AddBytesSent(nLenSending);
          return 1;
        }
      }
      return 0;
    }


    /// <summary>
    /// Check if incoming bytes are available on this connection.  If so, this
    /// function begins to read them in
    /// </summary>
    /// <returns>1 if new bytes were available, 0 if no new bytes were available</returns>
    public int CheckBytesIncoming()
    {
      if (fConnectionOpenOnDemand && commInterfaceToUse != null)
      {
        if (!commInterfaceToUse.PlayingBackData)
        {
          CommunicationsResponse resp = commInterfaceToUse.PollForNewDataFromServer();
          if ((resp & CommunicationsResponse.Success) != 0)
          {
            if ((resp & CommunicationsResponse.RejectedPresent) != 0)
            {
              commInterfaceToUse.SetHostResponseStatus(HostNegotiationStatus.RefusedToHost);
              resp -= CommunicationsResponse.RejectedPresent;
              MessageBox.Show("The user refused to take over the presentation");
            }
            else if ((resp & CommunicationsResponse.AcceptedPresent) != 0)
            {
              commInterfaceToUse.SetHostResponseStatus(HostNegotiationStatus.AcceptedHosting);
              resp -= CommunicationsResponse.AcceptedPresent;
              WebCommunicationsLibrary.fStopHosting = true;
              MessageBox.Show("The user accepted your request to host the presentation");
            }
            else if ((resp & CommunicationsResponse.AskToPresent) != 0)
            {
              DialogResult res = MessageBox.Show(
                "The host has asked you to present.  Press YES to accept, or NO to decline.", "Presentation Request",
                MessageBoxButtons.YesNo, MessageBoxIcon.Question, MessageBoxDefaultButton.Button1,
                MessageBoxOptions.DefaultDesktopOnly
                );
              if (res == DialogResult.Yes) { commInterfaceToUse.SetHostNegotiationStatus(HostNegotiationStatus.AcceptedHosting); }
              else if (res == DialogResult.No) { commInterfaceToUse.SetHostNegotiationStatus(HostNegotiationStatus.RefusedToHost); }
              else { commInterfaceToUse.SetHostNegotiationStatus(HostNegotiationStatus.None); }
            }
            return 1;
          }
          else
          {
            return 0;
          }
        }
        else return 0;
      }
      int nAvailable = 0;
      try
      {
        nAvailable = descriptor.Client.Available;
      }
      catch
      {
        NeedsClosing = true;
        return 0;
      }
      if (nAvailable > 0)// 2048)
      {
        if (nAvailable > 600000) nAvailable = 600000;
        int nReadSoFar = 0;
        int nRemaining = nAvailable;
        byte[] rgbIn = new byte[nAvailable];
        while (nRemaining > 0)
        {
          int nMaxToRead = (nRemaining > (32768 * 6)) ? (32768 * 6) : nRemaining;

          SocketError errCode;
          int nRead = descriptor.Client.Receive(rgbIn, nReadSoFar, nMaxToRead,
            SocketFlags.None, out errCode);

          if (errCode != SocketError.Success)
          {
            fNeedsClosing = true;
            break;
          }

          nReadSoFar += nRead;
          nRemaining -= nRead;
        }

        lock (rgIncomingBytes)
        {
          rgIncomingBytes.Enqueue(rgbIn);
        }

        AddBytesReceived(nAvailable);
        parentListener.AddBytesReceived(nAvailable);
        return nAvailable;
      }

      return 0;
    }

    public long AddBytesReceived(long nBytes)
    {
      return Interlocked.Add(ref nRecv, nBytes);
    }

    public long AddBytesReceived(int nBytes)
    {
      return Interlocked.Add(ref nRecv, (Int64)nBytes);
    }

    public long AddBytesSent(long nBytes)
    {
      return Interlocked.Add(ref nSent, nBytes);
    }

    public long AddBytesSent(int nBytes)
    {
      return Interlocked.Add(ref nSent, (Int64)nBytes);
    }
    /// <summary>
    /// Enqueue an array of bytes for sending to this connection
    /// </summary>
    /// <param name="rgByte">The bytes to send</param>
    public void EnqueueForSend(byte[] rgByte)
    {
      int nLen = rgByte.Length;
      int nRemaining = nLen;
      int nCurrentPosition = 0;
      List<byte[]> rgPackets = new List<byte[]>(32);
      // Limit the outgoing packet size
      const int nOutPacketSize = 8192 * 16;
      while (nRemaining > nOutPacketSize)
      {
        byte[] packet = new byte[nOutPacketSize];
        Array.Copy(rgByte, nCurrentPosition, packet, 0, nOutPacketSize);
        nRemaining -= nOutPacketSize;
        rgPackets.Add(packet);
        nCurrentPosition += nOutPacketSize;
      }
      if (nRemaining > 0)
      {
        byte[] packet = new byte[nRemaining];
        Array.Copy(rgByte, nCurrentPosition, packet, 0, nRemaining);
        rgPackets.Add(packet);
      }
      lock (rgOutgoingBytes)
      {
        for (int i = 0; i < rgPackets.Count; i++)
        {
          rgOutgoingBytes.Enqueue(rgPackets[i]);
        }
      }
    }
  }
}
